feat(compass): device-orientation compass widget (#163)#170
feat(compass): device-orientation compass widget (#163)#170jasoneplumb merged 3 commits intomainlinefrom
Conversation
Adds a top-right compass widget that shows where the device is physically facing relative to the (always-north-up) map. Most useful when stationary or at a junction — GPS course is NaN at low speeds, but the device compass works regardless. Helps the user align their physical orientation with the map's north-up frame. - src/orientation.ts: thin wrapper around DeviceOrientationEvent. extractHeading() prefers iOS webkitCompassHeading (true-north calibrated) and falls back to (360 - alpha). requestOrientation- Permission() handles the iOS 13+ user-gesture-only permission. subscribeOrientation() uses 'deviceorientationabsolute' when available so headings don't drift without calibration. - src/compass.ts: Leaflet control with an SVG compass rose. First tap requests permission; once granted, the rose rotates by -heading via a --heading-deg CSS custom property. Hidden when DeviceOrientationEvent is unavailable (desktop). - src/types.ts: lastDeviceHeadingDeg + compassPermission on AppState. - src/style.css: .compass-rose styles with active / unavailable variants and a 0.12s rotation transition for smooth tracking. - src/orientation.test.ts: 5 unit tests covering heading extraction edge cases (iOS preference, normalisation, NaN fallback). Closes #163 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code Review — feat(compass): device-orientation compass widgetOverall this is a clean, well-scoped feature. The API compatibility handling, iOS permission flow, and CSS-custom-property rotation pattern are all solid. A few things worth addressing before merge: Bugs / CorrectnessMissing The orientation subscription is only cleaned up via ```ts The
The field is written in Style / ConventionsMulti-line comment blocks ( CLAUDE.md says: "Never write multi-paragraph docstrings or multi-line comment blocks — one short line max." Both new files have multi-line JSDoc-style blocks above every function. These should be trimmed to a single line each (or removed entirely if the function name makes intent clear). Minor / Suggestions
```ts This checks for the event-handler property (which works in Chrome/Android) rather than the event type. A slightly more portable check is No iOS exposes Click handler attaches before the availability check ( The Test coverage ( The five Summary
The core logic ( |
- Add Leaflet onRemove() lifecycle hook to clean up the orientation subscription if the control is ever removed (was relying on map's 'unload' event which leaks if the listener is added/removed without a full map teardown). - Drop lastDeviceHeadingDeg from AppState — the field was written but never read. The visual rotation is driven directly via the --heading-deg CSS custom property; if a future feature needs the numeric value, it can be added then. - Trim multi-line JSDoc-style comment blocks in orientation.ts and compass.ts to single-line per CLAUDE.md convention. Skipped (advisory): - webkitCompassAccuracy gating — defer until field reports of jittery readings show it is needed. - 'DeviceOrientationAbsoluteEvent' in window detection vs 'ondeviceorientationabsolute' — current detection works in practice and the fallback path covers iOS regardless. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Cycle-2 fixes pushed:
Skipped:
|
Code Review — feat(compass): device-orientation compass widget (#163)OverviewClean, focused feature addition. The three-file split ( Issues1. PR description / diff discrepancy —
|
After permission is granted the click handler silently no-ops. The default cursor signals to the user that the compass is now passive and not waiting for further input. Per PR #170 cycle-2 review. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code Review — feat(compass): device-orientation compass widget (#163)OverviewClean, well-scoped feature. The separation into Bug — double-tap race condition in
|
Summary
NaNand the existing heading-cone wedge fades. Lets the user rotate themselves until the compass N points up — at that point their physical orientation matches the map.DeviceOrientationEvent.requestPermission()is handled inside the click handler; non-iOS browsers grant immediately. Hidden entirely when the API is unavailable.Closes #163
Files
New:
src/orientation.ts—extractHeading(),requestOrientationPermission(),subscribeOrientation().src/compass.ts— Leaflet control with an inline SVG rose; rotation driven by a--heading-degCSS custom property.src/orientation.test.ts— 5 vitest tests for heading-extraction edge cases (iOS preference, normalisation, NaN fallback).Modified:
src/types.ts—compassPermissiononAppState(cached so subsequent taps skip the prompt).src/main.ts— mounts the control after the existing top-right cluster.src/style.css—.compass-rose+--active/--unavailablevariants, 0.12 s rotation transition,cursor: defaultonce active.Behaviour
requestOrientationPermission()cursor: default, rose rotates with devicedeviceorientationabsolute(or fall back todeviceorientation)display: noneTest plan
npm run type-checkcleannpm run lintcleannpm testclean (633 tests, 5 new forextractHeading)npm run sizeclean (97.86 kB / 100 kB; +0.75 kB for the widget)Notes
alphais anti-clockwise around the device's z-axis. We flip it to clockwise (360 - alpha) for compass-bearing semantics. iOSwebkitCompassHeadingis already clockwise from True North so we use it directly when present.lastDeviceHeadingDegwas considered forAppStatebut dropped — no module currently reads it and visual rotation goes straight to a CSS custom property. Reintroduce if a future feature needs the numeric value.🤖 Generated with Claude Code